﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Xml.Linq;
using Volga_It.Hotels.Models;
using Volga_It.Hotels.Properties;

namespace Volga_It.Hotels.Controllers
{
    public class HomeController : Controller
    {
        /// <summary>
        /// Список сессий пользователя: значение cookie и результата поиска
        /// </summary>
        private static Dictionary<string, OutResultModels> _UserSessions = new Dictionary<string, OutResultModels>();
        /// <summary>
        /// Получить результаты поиска юзера по его сессии
        /// </summary>
        /// <returns>Экземпляр OutResultModels с результатами</returns>
        private OutResultModels GetSearchResultsByCookie()
        {
            OutResultModels resultModel = new OutResultModels();
            HttpCookie cookie = Request.Cookies.Get("SessionCookie");
            _UserSessions.TryGetValue(cookie.Value, out resultModel);
            return resultModel;
        }
        public ActionResult Index()
        {
            return View();
        }
        /// <summary>
        /// Метод контроллера поиска отелей по запросу
        /// </summary>
        /// <param name="searchModel">Экземпляр модели запроса поиска</param>
        /// <returns>Представление с результатом поиска</returns>
        [HttpPost]
        [ValidateAntiForgeryToken]        
        public ActionResult SearchRooms(SearchRequestModels searchModel)
        {
            OutResultModels outModel = new OutResultModels();
            outModel = DatabaseWork.GetOutResulForSearchRequest(searchModel);            
            string cookieVal = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(1, "SessionTicket", DateTime.Now,
                DateTime.Now.AddDays(1), false, outModel.GetHashCode().ToString()));
            HttpCookie cookie = new HttpCookie("SessionCookie", cookieVal);
            Response.SetCookie(cookie);
            _UserSessions.Add(cookieVal, outModel);
            return View(outModel);
        }
        /// <summary>
        /// Метод контроллера: сортировка результата по критерям, фильтрация, панджинация
        /// </summary>
        /// <param name="sort">Имя критерия, по которому сортируется результат</param>
        /// <param name="sortdir">Экземпляр модели предыдущего поискового запроса</param>
        /// <returns>Представление с отсортированной моделью</returns>
        public ActionResult SearchRooms(string sort, string sortdir, string areaFilter = "", int starsFilter = 0)
        {
            OutResultModels model = this.GetSearchResultsByCookie();
            switch (sort)
            {
                case "Name":
                    model.Hotels.Sort((x, y) => x.Name.CompareTo(y.Name));
                    break;
                case "Price":
                    if(sortdir == "ASC")
                        model.Rooms = model.Rooms.OrderBy(x => x.Price).ToList();
                    else model.Rooms = model.Rooms.OrderByDescending(x => x.Price).ToList();
                    break;
                case "Stars":
                    if (sortdir == "ASC")
                        model.Hotels = model.Hotels.OrderBy(x => x.Stars).ToList();
                    else model.Hotels = model.Hotels.OrderByDescending(x => x.Stars).ToList();
                    break;
            }
            if (areaFilter != null && areaFilter != "" || starsFilter != 0)
                model = Filter(model, areaFilter, starsFilter);
            HttpCookie cookie = Request.Cookies.Get("SessionCookie");
            _UserSessions[cookie.Value] = model;
            return View(model);
        }
       
        /// <summary>
        /// Метод фильтрации результата поиска по району и количеству звёзд
        /// </summary>
        /// <param name="model">Модель с результатом поиска отелей, которую надо отфильтровать</param>
        /// <param name="areaFilter">Название района, по которому фильтруются отели</param>
        /// <param name="starsFilter">Количество звёзд, по которому фильтруются отели</param>        
        /// <returns>Отфильтрованную модель с результатом поиска</returns>
        public OutResultModels Filter(OutResultModels model, string areaFilter = "", int starsFilter = 0)
        {            
            if(areaFilter != null && areaFilter != "")
                model.Hotels.RemoveAll(new Predicate<HotelModels>(x => x.Area != areaFilter));
            if (starsFilter != 0)
                model.Hotels.RemoveAll(new Predicate<HotelModels>(x => x.Stars != starsFilter));
            // Удаление номеров отелей, которых уже нет
            List<RoomModels> delRooms = new List<RoomModels>();    
            // Формируем список номеров на удаления, после чего удаляем их из модели (чтобы небыло изменения коллекции в операторе foreach)
            foreach (RoomModels currentRoom in model.Rooms)            
                if (model.Hotels.FirstOrDefault(x => x.HotelId == currentRoom.HotelId) == null)
                    delRooms.Add(currentRoom);        
            model.Rooms = model.Rooms.Except(delRooms.AsEnumerable()).ToList();
            ModelState.Clear();
            return model;
        }
        /// <summary>
        /// Метод контроллера просмотра подброной информации об отеле
        /// </summary>
        /// <param name="hotel">Имя отеля, для которого необходимо отобразить дополнительную инфу</param>
        /// <returns>Представление с дополнительной информацией об отеле</returns>
        public ActionResult Details(string hotel)
        {
            OutResultModels resultModel = this.GetSearchResultsByCookie();
            HotelDetailsModels model = new HotelDetailsModels(resultModel.Hotels.FirstOrDefault(x => x.Name == hotel));
            model.currentUserReview.HotelId = model.HotelId;
            model.previousReviews = DatabaseWork.LoadUsersReviewsForHotel(model);
            if (model.previousReviews.Count > 0)
            {
                int SumRating = 0;
                model.previousReviews.ForEach(x => SumRating += x.Rating);
                model.AvgRating = (float)SumRating / (float)model.previousReviews.Count;
            }
            return View(model);
        }
        /// <summary>
        /// Метод контроллера отправки отзыва о гостинице
        /// </summary>
        /// <param name="currentUserReview">Экземпляр UserReviewModel с полями отзыва юзера</param>
        /// <returns>Обновляет страницу Details</returns>
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult SendReview(UserReviewModel currentUserReview)
        {
            OutResultModels resultModel = this.GetSearchResultsByCookie();
            HotelDetailsModels model = new HotelDetailsModels(resultModel.Hotels.FirstOrDefault(x => x.HotelId == currentUserReview.HotelId));
            DatabaseWork.SendUserReview(currentUserReview);
            return RedirectToAction("Details", "Home", new { hotel = model.Name });
        }
        /// <summary>
        /// Метод контролерра просмотра информации о комнате с возможностью бронирования
        /// </summary>
        /// <param name="hotel">Имя гостиницы, которой принадлежит комната</param>
        /// <param name="room">Номер комнаты</param>
        /// <returns>Представление с моделью комнаты</returns>
        public ActionResult Booking(string hotel, string room)
        {
            OutResultModels resultModel = this.GetSearchResultsByCookie();
            RoomModels model = new RoomModels();
            model = resultModel.Rooms.FirstOrDefault(x => x.HotelName == hotel && x.RoomNumber == room);
            return View(model);
        }
        /// <summary>
        /// Метод отправки запроса бронирования комнаты
        /// </summary>
        /// <param name="RoomId">Id комнаты, которую юзер хочет забронировать</param>
        /// <param name="SelectedDate">Выбранная дата, которую юзер хочет забронировать</param>
        /// <returns>Обновляет страницу Booking</returns>
        [ValidateAntiForgeryToken]
        [HttpPost]
        public ActionResult BookRoom(int RoomId, DateTime SelectedDate)
        {
            OutResultModels resultModel = this.GetSearchResultsByCookie();
            RoomModels sessionRoom = resultModel.Rooms.FirstOrDefault(x => x.RoomId == RoomId);
            sessionRoom.AvailableDates.Remove(SelectedDate);
            DatabaseWork.BookRoom(RoomId, SelectedDate);
            ViewBag.ShowMessage = "Вы успешно забронировали комнату!";
            return View("Booking", sessionRoom);
        }
    }
   
    public static class DatabaseWork
    {
        /// <summary>
        /// Метод получения информации из БД данных о отелях с доступными номерами по критериям из модели поиска
        /// </summary>
        /// <param name="model">Экземпляр модели поиска, по критерям которой получаем строки из таблиц</param>
        /// <param name="starsFilter">Выбор отелей с данным количеством звёзд</param>
        /// <returns>Экземпляр модели результата</returns>
        public static OutResultModels GetOutResulForSearchRequest(SearchRequestModels model, int starsFilter = 0)
        {
            OutResultModels resultModel = new OutResultModels();
            using(SqlConnection conn = new SqlConnection(Settings.Default.DBConnectionString))
            {
                conn.Open();
                using(SqlTransaction trans = conn.BeginTransaction(IsolationLevel.ReadUncommitted))
                {
                    SqlCommand cmd = new SqlCommand("SELECT [Rooms].RoomId,Room,MaxPeople,RoomType,[Rooms].HotelId,SquaMet,[Rooms].ImageURL as RoomImage," +
                    "[Rooms].Description as RoomDescr,Hotels,Stars,Country,City,Area,WebSite,[Hotels].ImageURL as HotelImage,[Hotels].Description as HotelDescr,Date,[Price (RUB)],Available" +
                    " FROM Rooms INNER JOIN Hotels ON Rooms.HotelId=Hotels.HotelId " +
                    " INNER JOIN RoomsPrices ON Rooms.RoomId=RoomsPrices.RoomId" +
                    " WHERE Country=@Country AND City=@City AND [Date] >= @ArrivalTime AND [Date] <= @DepartureTime" +
                    " AND MaxPeople>=@PeoplesCount AND Available=1", conn, trans);
                    cmd.Parameters.AddWithValue("@Country", model.Country);
                    cmd.Parameters.AddWithValue("@City", model.City);
                    cmd.Parameters.AddWithValue("@PeoplesCount", model.PeoplesCount);
                    cmd.Parameters.Add("@ArrivalTime", SqlDbType.DateTime).Value = model.ArrivalTime;
                    cmd.Parameters.Add("@DepartureTime", SqlDbType.DateTime).Value = model.DepartureTime;                    
                    if (model.Area != null && model.Area != "")
                    {
                        cmd.CommandText += " AND Area=@Area";
                        cmd.Parameters.AddWithValue("@Area", model.Area);
                    }
                    if(starsFilter != 0)
                    {
                        cmd.CommandText += " AND Stars=@Stars";
                        cmd.Parameters.AddWithValue("@Stars", starsFilter);
                    }
                    SqlDataReader reader = cmd.ExecuteReader();
                    while(reader.Read())
                    {
                        HotelModels currentHotel = new HotelModels()
                        {
                            HotelId = (int)reader["HotelId"],
                            Name = (string)reader["Hotels"],
                            Stars = (short)reader["Stars"],
                            Country = (string)reader["Country"],
                            City = (string)reader["City"],
                            Area = (string)reader["Area"],
                            WebSite = (string)reader["WebSite"],
                            ImageURL = (string)reader["HotelImage"],
                            Description = (string)reader["HotelDescr"]
                        };
                        if(resultModel.Hotels.FirstOrDefault(x=>x.HotelId == currentHotel.HotelId) == null)
                            resultModel.Hotels.Add(currentHotel);
                        RoomModels room = new RoomModels()
                        {
                            RoomId = (int)reader["RoomId"],
                            RoomNumber = (string)reader["Room"],
                            RoomType = (string)reader["RoomType"],
                            MaxPeople = (int)reader["MaxPeople"],
                            HotelId = (int)reader["HotelId"],
                            HotelName = (string)reader["Hotels"],
                            ImageURL = (string)reader["RoomImage"],
                            Description = (string)reader["RoomDescr"],
                            Price = (int)reader["Price (RUB)"],                            
                        };
                        RoomModels existingRoom = resultModel.Rooms.FirstOrDefault(x=>x.RoomId == room.RoomId);
                        if (existingRoom == null)
                        {
                            room.AvailableDates.Add((DateTime)reader["Date"]);
                            resultModel.Rooms.Add(room);
                        }
                        else existingRoom.AvailableDates.Add((DateTime)reader["Date"]);
                    }
                    reader.Close();
                }
                conn.Close();
            }
            return resultModel;
        }
       /// <summary>
       /// Загрузить предыдущие оценки пользователей для отеля
       /// </summary>
       /// <param name="Hotel">Экземпляр HotelDetailsModels, для которого грузим оценки</param>
       /// <returns>Список UserReviewModel с оценками</returns>
        public static List<UserReviewModel> LoadUsersReviewsForHotel(HotelDetailsModels Hotel)
        {
            List<UserReviewModel> usersReviews = new List<UserReviewModel>();
            using(SqlConnection conn = new SqlConnection(Settings.Default.DBConnectionString))
            {
                conn.Open();
                using(SqlTransaction trans = conn.BeginTransaction(IsolationLevel.ReadUncommitted))
                {
                    SqlCommand cmd = new SqlCommand("SELECT * FROM HotelsReviews WHERE HotelId=@HotelId", conn, trans);
                    cmd.Parameters.AddWithValue("@HotelId", Hotel.HotelId);
                    SqlDataReader reader = cmd.ExecuteReader();
                    while(reader.Read())
                    {
                        UserReviewModel currentReview = new UserReviewModel()
                        {
                            Comment = (string)reader["Comment"],
                            HotelId = (int)reader["HotelId"],
                            Rating = (int)reader["Rating"],
                            ReviewId = (int)reader["ReviewId"],
                            Username = (string)reader["Username"]
                        };
                        usersReviews.Add(currentReview);
                    }
                    reader.Close();
                }
                conn.Close();
            }
            return usersReviews;
        }
        /// <summary>
        /// Отправка оценки пользователя для отеля
        /// </summary>
        /// <param name="review">Экземпляр UserReviewModel, имеющий HotelId отеля оценивания</param>
        public static void SendUserReview(UserReviewModel review)
        {
            using(SqlConnection conn = new SqlConnection(Settings.Default.DBConnectionString))
            {
                conn.Open();
                using(SqlTransaction trans = conn.BeginTransaction(IsolationLevel.ReadUncommitted))
                {
                    SqlCommand cmd = new SqlCommand("INSERT INTO HotelsReviews " +
                        "VALUES (@HotelId,@Rating,@Comment,@Username); SELECT SCOPE_IDENTITY();", conn, trans);
                    cmd.Parameters.AddWithValue("@HotelId", review.HotelId);
                    cmd.Parameters.AddWithValue("@Rating", review.Rating);
                    cmd.Parameters.AddWithValue("@Comment", review.Comment);
                    cmd.Parameters.AddWithValue("@Username", review.Username);
                    int id = 0;
                    if (Int32.TryParse(cmd.ExecuteScalar().ToString(), out id))
                        review.ReviewId = id;
                    trans.Commit();
                }
                conn.Close();
            }
            WriteReviewToCsv(review, Settings.Default.HotelsReviewsPath);
        }
        /// <summary>
        /// Запись оценки пользователя в CSV-файл
        /// </summary>
        /// <param name="review">Экземпляр UserReviewModel с оценкой пользователя и ReviewId</param>
        /// <param name="csvPath">Директория в проекте и имя файла</param>
        private static void WriteReviewToCsv(UserReviewModel review, string csvPath)
        {
            StringBuilder result = new StringBuilder();
            string newline = string.Format("{0};{1};{2};{3};{4}", review.ReviewId, review.HotelId,
                review.Rating, review.Comment, review.Username);
            result.Append(newline);
            File.AppendAllText(HttpRuntime.AppDomainAppPath + csvPath, result.ToString());
        }
        /// <summary>
        /// Метод бронирования номера
        /// </summary>
        /// <param name="RoomId">Id номера, который надо забронировать</param>
        /// <param name="Date">Дата, на которую нужно забронировать<</param>
        public static void BookRoom(int RoomId, DateTime Date)
        {
            using(SqlConnection conn = new SqlConnection(Settings.Default.DBConnectionString))
            {
                conn.Open();
                using(SqlTransaction trans = conn.BeginTransaction(IsolationLevel.ReadUncommitted))
                {
                    SqlCommand cmd = new SqlCommand("UPDATE RoomsPrices SET Available='False'" +
                    " FROM RoomsPrices WHERE RoomId=@RoomId AND Date=@Date", conn, trans);
                    cmd.Parameters.AddWithValue("@RoomId", RoomId);
                    cmd.Parameters.AddWithValue("@Date", Date);
                    cmd.ExecuteNonQuery();
                    trans.Commit();
                }
                conn.Close();
            }
            UpdateAvailableInCsv(RoomId, Date, Settings.Default.RoomsPricesDataPath);
        }
        /// <summary>
        /// Метод установки номера как забронированный в CSV-файле
        /// </summary>
        /// <param name="RoomId">Id номера, который надо забронировать</param>
        /// <param name="Date">Дата, на которую нужно забронировать</param>
        /// <param name="csvPath">Путь до CSV-файла в проекте</param>
        private static void UpdateAvailableInCsv(int RoomId, DateTime Date, string csvPath)
        {
            string[] fileLines = File.ReadAllLines(HttpRuntime.AppDomainAppPath + csvPath);
            int foundedIndex = -1;
            string[] columns = new string[] {""};
            foreach(string line in fileLines)
            {
                ++foundedIndex;
                columns = line.Split(';');
                if(columns[1] == RoomId.ToString() && columns[2] == Date.ToShortDateString())
                {
                    columns[4] = "False";
                    break;
                }
            }
            fileLines[foundedIndex] = string.Join(";", columns);
            File.WriteAllLines(HttpRuntime.AppDomainAppPath + csvPath, fileLines);
        }
    }
}
